/*
* This file is part of Project SkyFire https://www.projectskyfire.org.
* See LICENSE.md file for Copyright information
*/

#ifndef SKYFIRE_OBJECTACCESSOR_H
#define SKYFIRE_OBJECTACCESSOR_H

#include "Define.h"
#include "UnorderedMap.h"

#include "UpdateData.h"

#include "GridDefines.h"
#include "Object.h"

#include "ace/Singleton.h"

#include "SharedMutex.h"
#include <set>

class Creature;
class Corpse;
class Unit;
class GameObject;
class DynamicObject;
class WorldObject;
class Vehicle;
class Map;
class WorldRunnable;
class Transport;

template <class T>
class HashMapHolder
{
public:
    typedef UNORDERED_MAP<uint64, T*> MapType;

    static void Insert(T* o);

    static void Remove(T* o);

    static T* Find(uint64 guid);

    static MapType& GetContainer();

    static SF_SHARED_MUTEX* GetLock();


private:
    //Non instanceable only static
    HashMapHolder() { }
    static SF_SHARED_MUTEX i_lock;
    static MapType m_objectMap;
};

class ObjectAccessor
{
    friend class ACE_Singleton<ObjectAccessor, ACE_Null_Mutex>;
private:
    ObjectAccessor();
    ~ObjectAccessor();
    ObjectAccessor(const ObjectAccessor&);
    ObjectAccessor& operator=(const ObjectAccessor&);

public:
    /// @todo: Override these template functions for each holder type and add assertions
    template<class T> static T* GetObjectInOrOutOfWorld(uint64 guid, T* /*typeSpecifier*/)
    {
        return HashMapHolder<T>::Find(guid);
    }

    static Unit* GetObjectInOrOutOfWorld(uint64 guid, Unit* /*typeSpecifier*/)
    {
        if (IS_PLAYER_GUID(guid))
            return (Unit*)GetObjectInOrOutOfWorld(guid, (Player*)NULL);

        if (IS_PET_GUID(guid))
            return (Unit*)GetObjectInOrOutOfWorld(guid, (Pet*)NULL);

        return (Unit*)GetObjectInOrOutOfWorld(guid, (Creature*)NULL);
    }

    // returns object if is in world
    template<class T> static T* GetObjectInWorld(uint64 guid, T* /*typeSpecifier*/)
    {
        return HashMapHolder<T>::Find(guid);
    }

    // Player may be not in world while in ObjectAccessor
    static Player* GetObjectInWorld(uint64 guid, Player* /*typeSpecifier*/);

    static Unit* GetObjectInWorld(uint64 guid, Unit* /*typeSpecifier*/)
    {
        if (IS_PLAYER_GUID(guid))
            return (Unit*)GetObjectInWorld(guid, (Player*)NULL);

        if (IS_PET_GUID(guid))
            return (Unit*)GetObjectInWorld(guid, (Pet*)NULL);

        return (Unit*)GetObjectInWorld(guid, (Creature*)NULL);
    }

    // returns object if is in map
    template<class T> static T* GetObjectInMap(uint64 guid, Map* map, T* /*typeSpecifier*/)
    {
        ASSERT(map);
        if (T* obj = GetObjectInWorld(guid, (T*)NULL))
            if (obj->GetMap() == map)
                return obj;
        return NULL;
    }

    template<class T> static T* GetObjectInWorld(uint32 mapid, float x, float y, uint64 guid, T* /*fake*/);

    // these functions return objects only if in map of specified object
    static WorldObject* GetWorldObject(WorldObject const&, uint64);
    static Object* GetObjectByTypeMask(WorldObject const&, uint64, uint32 typemask);
    static Corpse* GetCorpse(WorldObject const& u, uint64 guid);
    static GameObject* GetGameObject(WorldObject const& u, uint64 guid);
    static Transport* GetTransport(WorldObject const& u, uint64 guid);
    static DynamicObject* GetDynamicObject(WorldObject const& u, uint64 guid);
    static AreaTrigger* GetAreaTrigger(WorldObject const& u, uint64 guid);
    static Unit* GetUnit(WorldObject const&, uint64 guid);
    static Creature* GetCreature(WorldObject const& u, uint64 guid);
    static Pet* GetPet(WorldObject const&, uint64 guid);
    static Player* GetPlayer(WorldObject const&, uint64 guid);
    static Creature* GetCreatureOrPetOrVehicle(WorldObject const&, uint64);

    // these functions return objects if found in whole world
    // ACCESS LIKE THAT IS NOT THREAD SAFE
    static Pet* FindPet(uint64);
    static Player* FindPlayer(uint64);
    static Player* FindPlayerInOrOutOfWorld(uint64);
    //static Creature* FindCreature(uint64);
    static Unit* FindUnit(uint64);
    static Player* FindPlayerByName(std::string const& name);

    // when using this, you must use the hashmapholder's lock
    static HashMapHolder<Player>::MapType const& GetPlayers()
    {
        return HashMapHolder<Player>::GetContainer();
    }

    // when using this, you must use the hashmapholder's lock
    static HashMapHolder<Creature>::MapType const& GetCreatures()
    {
        return HashMapHolder<Creature>::GetContainer();
    }

    // when using this, you must use the hashmapholder's lock
    static HashMapHolder<GameObject>::MapType const& GetGameObjects()
    {
        return HashMapHolder<GameObject>::GetContainer();
    }

    template<class T> static void AddObject(T* object)
    {
        HashMapHolder<T>::Insert(object);
    }

    template<class T> static void RemoveObject(T* object)
    {
        HashMapHolder<T>::Remove(object);
    }

    static void SaveAllPlayers();

    //non-static functions
    void AddUpdateObject(Object* obj);
    void RemoveUpdateObject(Object* obj);

    //Thread safe
    Corpse* GetCorpseForPlayerGUID(uint64 guid);
    void RemoveCorpse(Corpse* corpse);
    void AddCorpse(Corpse* corpse);
    void AddCorpsesToGrid(GridCoord const& gridpair, GridType& grid, Map* map);
    Corpse* ConvertCorpseForPlayer(uint64 player_guid, bool insignia = false);

    //Thread unsafe
    void Update(uint32 diff);
    void RemoveOldCorpses();
    void UnloadAll();

private:
    typedef UNORDERED_MAP<uint64, Corpse*> Player2CorpsesMapType;
    typedef UNORDERED_MAP<Player*, UpdateData>::value_type UpdateDataValueType;

    std::set<Object*> i_objects;
    Player2CorpsesMapType i_player2corpse;

    SF_SHARED_MUTEX i_objectLock;
    SF_SHARED_MUTEX i_corpseLock;
};

#define sObjectAccessor ACE_Singleton<ObjectAccessor, ACE_Null_Mutex>::instance()

#endif
